# Copyright (c) 2009, Jesse Kempf <jessekempf@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# - Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
# - Neither the names of the copyright holders nor the names of the contributors
#     may be used to endorse or promote products derived from this software
#     without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
#  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
#  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
#  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
#  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""A watchdog timer."""

import os
import threading

class WatchdogTimer(object):
	"""A simple watchdog timer.

	A watchdog timer typically runs alongside the rest of the program
	and shuts the process down if it's not periodically sent a keepalive
	message within a defined grace period. This can be used to force a
	process to die rather than go off into never-never land in the event
	of software failure.

	WatchdogTimer requires threads to function.

	Creating a watchdog requires the following arguments:
		delay: The number of seconds the timer can run without a keepalive
			before it terminates the running process.
	And can take the following optional arguments:
		on_trigger: A zero-argument function that will be called before
			process shutdown if the watchdog fires.

	Once a watchdog is created, calling the start() method will activate it.
	While it's running, a call to the reset() method will send a keepalive.
	If reset() hasn't been called during the grace period indicated by delay,
	the process will be terminated by a call to os._exit(). The watchdog timer
	can be halted by calling the stop() method. A call to stop() will leave
	the timer in a state where it can be started again using start().
	"""

	def __init__(self, delay, on_trigger=None):
		"""Create a watchdog timer with a grace period given by `delay'."""
		self.delay = delay
		self.timer = None
		self._mktimer()

		self.on_trigger = on_trigger or (lambda: None)

	def _run(self):
		"""The function that's called when the timer runs out."""
		self.on_trigger()

		# pylint: disable-msg=W0212

		os._exit(os.EX_SOFTWARE)

		# pylint: enable-msg=W0212

	def _mktimer(self):
		"""Create a timer that will execute self._run if it runs out."""
		self.timer = threading.Timer(self.delay, self._run)

	def start(self):
		"""Start the watchdog timer."""
		self.timer.start()

	def is_alive(self):
		"""Check to see if the timer is running."""
		return self.isAlive()

	# pylint: disable-msg=C0103
	def isAlive(self):
		"""Check to see if the timer is running."""
		return self.timer.isAlive()
	# pylint: enable-msg=C0103

	def reset(self):
		"""Send the timer a keepalive."""
		self.stop()
		self.start()

	def stop(self):
		"""Stop the watchdog."""
		self.timer.cancel()
		self._mktimer()
